#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import asyncio
import aiomysql
import logging

async def create_pool(loop, **kw):
    logging.info('create database connection pool...')
    global __pool
    __pool = await aiomysql.create_pool(
        host=kw.get('host', 'localhost'),
        port=kw.get('port', 3306),
        user=kw['user'],
        password=kw['password'],
        db=kw['db'],
        charset=kw.get('charset', 'utf8'),
        autocommit=kw.get('autocommit', True),
        maxsize=kw.get('maxsize', 10),
        minsize=kw.get('minsize', 1),
        loop=loop
    )

async def create__pool(**kw):
    global __pool
    __pool= await aiomysql.create_pool(
        host= kw.get('host', 'localhost'),
        port= kw.get('port', 3306),
        user= kw['user'],
        password= kw['password'],
        db= kw['db'],
        charset= kw.get('charset', 'utf-8'),
        autocommit = kw.get('autocommit',True),
        maxsize= kw.get('maxsize', 10),
        minsize= kw.get('minsize', 1),
    )

async def select(sql,args,size= None):
    global create__pool
    with (await __pool) as conn:
        cur = await conn.cursor(aiomysql.DictCursor)
        await cur.execute(sql.replace('?','%s'), args)
        if size:
            rs = await cur.fetchmany(size)
        else:
            rs = await cur.fetchall()
        await cur.close()
        return rs

async def execute(sql,args):
    global __pool
    try:
        with (await __pool) as conn:
            cur = await conn.cursor()
            await cur.execute(sql.replace('?','%s'), args)
            affected= cur.rowcount
            await cur.close()
    except BaseException as e:
        raise e
    return affected

class Model(object):
    async def find(self):
        pass



class Field(object):
    def __init__(self,name, column_type, primary_key, default):
        self.name = name
        self.column_type = column_type
        self.primary_key = primary_key
        self.default = default
    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__,self.name)

class StringField(Field):
    def __init__(self,name= None ,primary_key = False,  default = None ,ddl = 'varchar(100'):
        super(StringField,self).__init__(name,ddl,primary_key,default)

class IntegerField(Field):
    def __init__(self, name=None, primary_key=False, default=0):
        super().__init__(name, 'bigint', primary_key, default)

class BooleanField(Field):
    def __init__(self, name = None, default = False):
        super().__init__(name,'boolean',False,default)

class FloatField(Field):
    def __init__(self, name=None, primary_key=False, default=0.0):
        super().__init__(name, 'real', primary_key, default)

class TextField(Field):
    def __init__(self, name=None, default=None):
        super().__init__(name, 'text', False, default)

class ModelMetaClass(type):
    def __new__(cls,name,bases,attrs):
        if name =='Model':
            return type.__new__(cls,name,bases,attrs)
        tableName= attrs.get('__table__',None) or name
        logging.info('found model: %s (table: %s' %(name, tableName))
        mappings = dict()
        fields = []
        primaryKey= None
        for k,v in attrs.items():
            if isinstance(v,Field):
                mappings[k] = v
                if v.primary_key:
                    if primaryKey:
                        raise RuntimeError('Douplicate primary key for field :%s ' % k)
                    primaryKey=k
                else:
                    fields.append(k)
        if not primaryKey:
            raise RuntimeError('Primary key not found')
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings
        attrs['__table__'] = tableName
        attrs['__primaryKey__'] = primaryKey
        attrs['__fields__'] = fields
        attrs['__select__'] = 'select %s,%s from %s'% (primaryKey,','.join(map(lambda f: '%s' % (mappings.get(f).name or f),fields )),tableName)
        attrs['__update__'] = 'update %s set %s where %s = ?' % (tableName,', '.join(map(lambda f: '`%s`=? '% (mappings.get(f).name or f),fields)),primaryKey)
        attrs['__insert__'] = 'insert into %s (%s,%s) values (%s);' % (tableName,primaryKey,','.join(map(lambda f: '%s' % (mappings.get(f).name or f),fields)),create_args_string(len(fields)+1))
        attrs['__delete__'] = 'delete from %s where %s = ?;' % (tableName,primaryKey)
        return type.__new__(cls,name,bases,attrs)

class Model(dict, metaclass= ModelMetaClass):
    def __init__(self,**kw):
        super(Model,self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

    def getValue(self,key):
        value = getattr(self, key, None)
        if value is None:
            field = self.__mappings__[key]
            if field.default is not None:
                value= field.default() if callable(field.default) else field.default
                setattr(self,key,value)
            return value

    @asyncio.coroutine
    def save(self):
        args = list(map(self.getValueOrDefault,self.__mappings__))
        yield from execute(self.__insert__,args)

    @asyncio.coroutine
    def remove(self):
        args = []
        args.append(self[self.__primaryKey__])
        print(self.__delete__)
        yield from execute(self.__delete__,args)

    @asyncio.coroutine
    def update(self, **kw):
        print("enter update")
        args= []
        for key in kw:
            if key not in self.__fields__:
                raise RuntimeError('field not found')
        for key in self.__fields__:
            if key in kw:
                args.append(kw[key])
            else:
                args.append(getattr(self,key,None))
        args.append(getattr(self,self.__primary_key__))
        yield from execute(self.__update__,args)

    @classmethod
    @asyncio.coroutine
    def find(cls,pk):
        rs = yield from select('%s where `%s`=?' % (cls.__select__,cls.__primaryKey__),[pk],1)
        if len(rs) == 0:
            return None
        return cls(**rs[0])

    @classmethod
    @asyncio.coroutine
    def findAll(cls,where = None,args=None):
        sql = [cls.__select__]
        if where:
            sql.append('where')
            sql.append(where)
        if args is None:
            args = []
        rs = yield from select(' '.join(sql),args)
        return [cls(**r) for r in rs]

class User(Model):
    __table__ = "user"
    id = StringField()
    name = StringField()
user = User(id ='10001', name = 'Andy')
user.save()